package edi.android.tmdbBrowser;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import net.sf.jtmdb.BrowseOptions;
import net.sf.jtmdb.Movie;
import net.sf.jtmdb.BrowseOptions.ORDER;
import net.sf.jtmdb.BrowseOptions.ORDER_BY;

import org.json.JSONException;

import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * Auxillary class to download movies in background and cache them
 * 
 * @author edi
 */
public class MovieDownloader {
	public final static int STATUS_OK = 0;
	public final static int STATUS_IO_ERROR = 1;
	public final static int STATUS_PARSE_ERROR = 2;
	private static final String LOG_TAG = MovieDownloader.class.getSimpleName();
	private static List<Movie> top10list;
	private static HashMap<Integer, Movie> movieCache;
	private static Top10DownloaderTask top10DownloaderTask;
	private static List<Movie> movieSearchResult;

	public MovieDownloader() {
		super();
		movieCache = new HashMap<Integer, Movie>();
	}

	/**
	 * Get the "top10" list for the main screen. This will be done in background
	 * and connected to the gallery as soon as downloaded
	 * 
	 * @param gallery
	 *            The gallery where the top 10 posters should be drawn
	 * @param context
	 *            The context to which the gallery belongs
	 * @param stopProgressHandler
	 *            Handler to stop the progress bar in the main screen
	 */
	public void getTop10(Handler stopProgressHandler) {
		if (top10list != null) {
			stopProgressHandler.dispatchMessage(new Message());
		} else if (top10DownloaderTask == null) {
			// TODO: implement some mechanism to prevent that two threads are
			// running here...
			top10DownloaderTask = new Top10DownloaderTask(stopProgressHandler);
			top10DownloaderTask.execute();
		} else {
			top10DownloaderTask.setHandlerReference(new WeakReference<Handler>(
					stopProgressHandler));
		}
	}

	/**
	 * Task to fetch the top 10 list and bind it to the gallery
	 * 
	 * @param g
	 *            Gallery to bind the top 10 to
	 */
	class Top10DownloaderTask extends AsyncTask<Void, Void, List<Movie>> {

		private Message message;

		private WeakReference<Handler> handlerReference;

		public Top10DownloaderTask(Handler stopProgressHandler) {
			handlerReference = new WeakReference<Handler>(stopProgressHandler);
			message = new Message();
			message.arg1 = STATUS_OK;
		}

		/**
		 * Actual download method.
		 */
		@Override
		protected List<Movie> doInBackground(Void... arg0) {

			// try to get the top10 from the net
			try {
				// TODO: reset after trials
				// return Movie.mostPopular();
				return Movie.mostPopular();
			} catch (IOException e) {
				Log.e(LOG_TAG, "IO Error", e);
				message.arg1=STATUS_IO_ERROR;
				return null;
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				Log.e(LOG_TAG, "JSON Error", e);
				message.arg1=STATUS_PARSE_ERROR;
				return null;
			}
		}

		/**
		 * Once the list is downloaded, set the gallery adapter
		 */
		@Override
		protected void onPostExecute(List<Movie> movieList) {

			Handler stopProgressHandler = handlerReference.get();

			// save the list for the next inquiry
			top10list = movieList;

			// stop the progress bar
			try {
				stopProgressHandler.dispatchMessage(message);
			} catch (Exception e) {
				Log.w(LOG_TAG, "Handler callback failed", e);
			}

			// add the movies to the cache, if there are any
			if (movieList != null) {
				Iterator<Movie> movieIterator = movieList.iterator();
				Movie movie;
				while (movieIterator.hasNext()) {
					movie = movieIterator.next();
					if (!movieCache.containsKey(movie.getID()))
						synchronized (movieCache) {
							movieCache.put(movie.getID(), movie);
						}
				}
			}
		}

		/**
		 * @param handlerReference
		 *            the handlerReference to set
		 */
		public void setHandlerReference(WeakReference<Handler> handlerReference) {
			this.handlerReference = handlerReference;
		}
	}

	/**
	 * Get the top10 cached list
	 * 
	 * @return the top10list
	 */
	public List<Movie> getTop10list() {
		return top10list;
	}

	/**
	 * Get a movie. If it's not in the cache, download it. Move the movie to top
	 * of the cache, so next time it will be returned without downloading. Also
	 * fetches complete info if the movie was reduced.
	 * 
	 * @param movieID
	 *            which movie to receive
	 * @return the movie
	 */
	public static Movie get(int movieID) {
		Movie movie = null;

		// check if the movie is in the movie cache
		if (movieCache.containsKey(movieID)) {
			movie = movieCache.get(movieID);
			movieCache.remove(movieID);
		}

		// check if the movie is null or reduced
		if (movie == null || movie.isReduced()) {

			try {
				movie = Movie.getInfo(movieID);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				Log.w(LOG_TAG, "IOException while fetching movie ID " + movie,
						e);
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				Log.w(LOG_TAG,
						"JSONException while fetching movie ID " + movie, e);

			}
		}

		// only cache the movie if it is not null
		if (movie != null) {
			movieCache.put(movieID, movie);
		}
		return movie;
	}

	/**
	 * This method searches for movies and puts the result to a persistent field
	 * 
	 * @param searchString
	 *            The search string to search movies for
	 */
	public void searchMovie(String searchString) {
		movieSearchResult = null;
		BrowseOptions browseOptions = new BrowseOptions();
		browseOptions.setOrder(ORDER.DESC);
		browseOptions.setOrderBy(ORDER_BY.RATING);
		browseOptions.setQuery(searchString);
		try {
			movieSearchResult = Movie.search(searchString);
			Log.w(LOG_TAG, "" + movieSearchResult.size() + "movies for "
					+ searchString);
		} catch (IOException e) {
			Log.w(LOG_TAG, "IOException while searching movie with "
					+ searchString, e);
		} catch (JSONException e) {
			Log.w(LOG_TAG, "JSONException while searching movie with "
					+ searchString, e);
		}
	}

	/**
	 * @return the last movie search result
	 */
	public List<Movie> getLastMovieResult() {
		return movieSearchResult;
	}

}
